package com.guinea.generator;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.guinea.core.utils.StringUtils;

import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;

public class Config {
	static final String CONFIG_PATH = "config";

	static final String OUTPUT_ROOT_PATH = "/generated";

	static final String SQL_PATH = "/sql";

	static final String JAVA_PATH = "/src/main/java";

	static final String RESOURCE_PATH = "/src/main/resources/modules";

	static final String JSP_PATH = "/src/main/webapp/WEB-INF/modules";
	
	static final String dbType = "-oracle";

	List modules;

	List classes;

	String configPath;

	// String templatePath;

	String outputPath;

	Configuration freemarkerEngine;

	public Config() {
		super();
		String binPath = Config.class.getClassLoader().getResource(".")
				.getPath();
		String docPath = binPath;
		File f = new File(binPath + "gp.properties");
		Properties p = new Properties();
		if (f.canRead())
			try {
				p.load(new FileInputStream(f));
				docPath = StringUtils.defaultString(p.getProperty("path"),
						binPath);
			} catch (FileNotFoundException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		configPath = binPath + CONFIG_PATH;
		outputPath = docPath + OUTPUT_ROOT_PATH;

		// templatePath = "D:/p/ws3/skoru/doc/template";

		freemarkerEngine = new Configuration();
		freemarkerEngine.setDefaultEncoding("UTF-8");

		// try {
		// TemplateLoader l = new FileTemplateLoader(new File(templatePath));
		TemplateLoader l = new ClassTemplateLoader(Config.class, "/template");
		freemarkerEngine.setTemplateLoader(l);
		// } catch (IOException e) {
		// e.printStackTrace();
		// }
		this.modules = new ArrayList();
		this.classes = new ArrayList();
		loadConfig(configPath);
	}

	private void loadConfig(String configPath) {

		// Build a document from the file
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			File configDir = new File(configPath);
			if (!configDir.exists() || !configDir.isDirectory()
					|| configDir.list() == null)
				return;
			File[] fs = configDir.listFiles();
			for (int i = 0; i < fs.length; i++) {
				File f = fs[i];
				if (f.isFile()) {
					Document document = builder.parse(f);
					parseConfig(document);
				}
			}

		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	private void parseConfig(Document document) {
		Element root = document.getDocumentElement();
		MappingModule module = new MappingModule(getAttribute(root,
				"total-package"), getAttribute(root, "module-name"),
				getAttribute(root, "module-title"));
		List currentClasses = new ArrayList();
		Element classElement = null;
		Element propertyElement = null;
		Element formElement = null;
		Element oneToOneElement = null;
		Element oneToManyElement = null;
		Element manyToOneElement = null;

		NodeList classNodes = root.getElementsByTagName("class");
		for (int i = 0; i < classNodes.getLength(); i++) {
			classElement = (Element) classNodes.item(i);
			// class
			MappingClass c = new MappingClass(
					getAttribute(classElement, "name"), getAttribute(
							classElement, "title"), getAttribute(classElement,
							"prefix"), getAttribute(classElement, "table"),
					getAttribute(classElement, "is-sequence"), getAttribute(
							classElement, "is-crud"));
			c.setModule(module);

			// propertyList
			List propertyList = new ArrayList();
			NodeList propertyNodes = classElement
					.getElementsByTagName("property");
			for (int j = 0; j < propertyNodes.getLength(); j++) {
				// property
				propertyElement = (Element) propertyNodes.item(j);
				MappingProperty p = new MappingProperty(getAttribute(
						propertyElement, "name"), getAttribute(propertyElement,
						"type"), getAttribute(propertyElement, "show"),
						getAttribute(propertyElement, "search"), getAttribute(
								propertyElement, "dict"), getAttribute(
								propertyElement, "column"), getAttribute(
								propertyElement, "length"), getAttribute(
								propertyElement, "precision"), getAttribute(
								propertyElement, "scale"), getAttribute(
								propertyElement, "default"), getAttribute(
								propertyElement, "not-null"), getAttribute(
								propertyElement, "unique"));
				p.setParent(c);

				// form-element
				NodeList formElementList = propertyElement
						.getElementsByTagName("form-element");
				MappingFormElement f = null;
				if (formElementList.getLength() > 0) {
					formElement = (Element) formElementList.item(0);
					f = new MappingFormElement(getAttribute(formElement,
							"title"), getAttribute(formElement, "default"),
							getAttribute(formElement, "required"),
							getAttribute(formElement, "minlength"),
							getAttribute(formElement, "maxlength"),
							getAttribute(formElement, "unique"), getAttribute(
									formElement, "date-format"), getAttribute(
									formElement, "email"), getAttribute(
									formElement, "anyphone"), getAttribute(
									formElement, "zip"), getAttribute(
									formElement, "url"), getAttribute(
									formElement, "precision"), getAttribute(
									formElement, "range"));
					f.setParent(p);
				}
				p.setElement(f);

				propertyList.add(p);
			}
			c.setProperties(propertyList);

			// oneToOneList
			List oneToOneList = new ArrayList();
			NodeList oneToOneNodes = classElement
					.getElementsByTagName("one-to-one");
			for (int j = 0; j < oneToOneNodes.getLength(); j++) {
				// one-to-one
				oneToOneElement = (Element) oneToOneNodes.item(j);
				MappingOneToOne o = new MappingOneToOne(getAttribute(
						oneToOneElement, "name"), getAttribute(oneToOneElement,
						"class"), getAttribute(oneToOneElement, "column"),
						getAttribute(oneToOneElement, "inverse"));
				o.setParent(c);

				oneToOneList.add(o);
			}
			c.setOneToOne(oneToOneList);

			// oneToManyList
			List oneToManyList = new ArrayList();
			NodeList oneToManyNodes = classElement
					.getElementsByTagName("one-to-many");
			for (int j = 0; j < oneToManyNodes.getLength(); j++) {
				// one-to-many
				oneToManyElement = (Element) oneToManyNodes.item(j);
				MappingOneToMany o = new MappingOneToMany(getAttribute(
						oneToManyElement, "name"), getAttribute(
						oneToManyElement, "class"));
				o.setParent(c);

				oneToManyList.add(o);
			}
			c.setOneToMany(oneToManyList);

			// manyToOneList
			List manyToOneList = new ArrayList();
			NodeList manyToOneNodes = classElement
					.getElementsByTagName("many-to-one");
			for (int j = 0; j < manyToOneNodes.getLength(); j++) {
				// many-to-one
				manyToOneElement = (Element) manyToOneNodes.item(j);
				MappingManyToOne m = new MappingManyToOne(getAttribute(
						manyToOneElement, "name"), getAttribute(
						manyToOneElement, "class"), getAttribute(
						manyToOneElement, "column"));
				m.setParent(c);

				manyToOneList.add(m);
			}
			c.setManyToOne(manyToOneList);
			c.setConfig(this);

			currentClasses.add(c);
			classes.add(c);
		}

		module.setClasses(currentClasses);
		modules.add(module);
	}

	public void generate() {
		String allSql = "";
		String allMenuData = "";
		for (Iterator iter = modules.iterator(); iter.hasNext();) {
			MappingModule m = (MappingModule) iter.next();
			String mname = m.getModuleName();
			String mPath = "/" + mname;
			String content;
			content = produceToStringModule("all-sql"+dbType+".ftl", m);
			createFile(content, mname + "-all.sql", outputPath
					+ Config.SQL_PATH + mPath);
			allSql += content;
			content = produceToStringModule("all-menu-data.ftl", m);
			allMenuData += content;
			createFile(content, mname + "-menu-data.sql", outputPath
					+ Config.SQL_PATH + mPath);
			content = produceToStringModule("message.ftl", m);
			createFile(content, "messages_" + mname + "_zh_CN.properties",
					outputPath + Config.RESOURCE_PATH + mPath);
			content = produceToStringModule("spring.ftl", m);
			createFile(content, "spring-" + mname + ".xml", outputPath
					+ Config.RESOURCE_PATH + mPath);
			content = produceToStringModule("struts.ftl", m);
			createFile(content, "struts-" + mname + ".xml", outputPath
					+ Config.RESOURCE_PATH + mPath);
			content = produceToStringModule("validator.ftl", m);
			createFile(content, "validator-" + mname + ".xml", outputPath
					+ Config.RESOURCE_PATH + mPath);
			content = produceToStringModule("index.ftl", m);
			createFile(content, "index.jsp", outputPath + Config.JSP_PATH
					+ mPath);
		}
		createFile(allSql, "all-sql.sql", outputPath + Config.SQL_PATH);
		createFile(allMenuData, "all-menu-data.sql", outputPath
				+ Config.SQL_PATH);
		for (Iterator iter = classes.iterator(); iter.hasNext();) {
			MappingClass c = (MappingClass) iter.next();
			String mname = c.getModule().getModuleName();
			String packagePath = "/"
					+ c.getModule().getTotalPackage().replaceAll("\\.", "/");
			String m = "/" + mname;
			packagePath += m;
			String lname = StringUtils.uncapitalize(c.getName());
			String content;
			content = produceToString("ddl"+dbType+".ftl", c);
			createFile(content, lname + ".sql", outputPath + Config.SQL_PATH
					+ m);
			content = produceToString("entity.ftl", c);
			createFile(content, c.getName() + ".java", outputPath
					+ Config.JAVA_PATH + packagePath + "/entity");
			content = produceToString("dao.ftl", c);
			createFile(content, c.getName() + "Dao.java", outputPath
					+ Config.JAVA_PATH + packagePath + "/dao");
			content = produceToString("service.ftl", c);
			createFile(content, c.getName() + "Service.java", outputPath
					+ Config.JAVA_PATH + packagePath + "/service");
			content = produceToString("form.ftl", c);
			createFile(content, c.getName() + "Form.java", outputPath
					+ Config.JAVA_PATH + packagePath + "/form");
			content = produceToString("action.ftl", c);
			createFile(content, c.getName() + "Action.java", outputPath
					+ Config.JAVA_PATH + packagePath + "/action");
			content = produceToString("hbm.ftl", c);
			createFile(content, c.getName() + ".hbm.xml", outputPath
					+ Config.RESOURCE_PATH + m + "/hbm");
			content = produceToString("jsp-list.ftl", c);
			createFile(content, lname + "-list.jsp", outputPath
					+ Config.JSP_PATH + m);
			content = produceToString("jsp-view.ftl", c);
			createFile(content, lname + "-view.jsp", outputPath
					+ Config.JSP_PATH + m);
			content = produceToString("jsp-edit.ftl", c);
			createFile(content, lname + "-edit.jsp", outputPath
					+ Config.JSP_PATH + m);

		}

	}

	public void createFile(String content, String fileName, String path) {
		File f = new File(path);
		if (!f.exists())
			f.mkdirs();
		OutputStreamWriter fw = null;
		try {
			fw = new OutputStreamWriter(new FileOutputStream(new File(path,
					fileName)), "UTF-8");
			fw.write(content);
		} catch (IOException io) {
			io.printStackTrace();
		} finally {
			if (fw != null) {
				try {
					fw.flush();
					fw.close();
				} catch (IOException io) {
					io.printStackTrace();
				}
			}
		}
	}

	private String produceToString(String templateName, MappingClass c) {
		StringWriter tempWriter = new StringWriter();
		BufferedWriter bw = new BufferedWriter(tempWriter);
		// First run - writes to in-memory string
		Map m = new HashMap();
		m.put("c", c);
		m.put("config", this);
		Template t;
		try {
			t = freemarkerEngine.getTemplate(templateName);
			t.process(m, bw);
		} catch (TemplateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			bw.flush();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("Error while flushing to string", e);
		}
		return tempWriter.toString();
	}

	private String produceToStringModule(String templateName, MappingModule m) {
		StringWriter tempWriter = new StringWriter();
		BufferedWriter bw = new BufferedWriter(tempWriter);
		// First run - writes to in-memory string
		Map mm = new HashMap();
		mm.put("m", m);
		mm.put("config", this);
		Template t;
		try {
			t = freemarkerEngine.getTemplate(templateName);
			t.process(mm, bw);
		} catch (TemplateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			bw.flush();
		} catch (IOException e) {
			e.printStackTrace();
			throw new RuntimeException("Error while flushing to string", e);
		}
		return tempWriter.toString();
	}

	private String getAttribute(Element element, String name) {
		if (element != null && element.getAttribute(name) != null
				&& element.getAttribute(name).trim() != "") {
			return element.getAttribute(name).trim();
		} else {
			return null;
		}
	}

	public List getClasses() {
		return classes;
	}

	public void setClasses(List classes) {
		this.classes = classes;
	}

	public List getModules() {
		return modules;
	}

	public void setModules(List modules) {
		this.modules = modules;
	}

	public static void main(String[] a) {
		Config c = new Config();
		c.generate();
		System.out.println("ok");
	}
}
